home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 7: Sunsite
/
Linux Cubed Series 7 - Sunsite Vol 1.iso
/
search
/
lsmtool-.6
/
lsmtool-
/
lsmtool-0.6
/
tool.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-01-01
|
16KB
|
813 lines
/*
* tool.c -- user interface for an LSM database
*
* Lars Wirzenius
* "@(#)lsmtool:tool.c,v 1.14 1995/01/01 16:04:17 wirzeniu Exp"
*/
#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <publib.h>
#include "lsm.h"
#include "term.h"
/*
* Help screen.
*/
static void help(void) {
static const char *txt[] = {
"LSMTOOL by Lars Wirzenius",
"",
"h, H Help",
"q, Q Quit",
"n, N Next entry",
"p, P Previous entry",
"/ Search forward",
"?, \\ Search backward",
"spc Next page",
"- Previous page",
"return,",
"+, i, I switch between list and entry modes",
"g First entry",
"G Last entry (notice differece in case!)",
"w, W Write entry to file",
"d, D Delete entry",
"s, S Save database into file (ask for name)",
"$ Sort the database",
"u, U Find next dUplicate (same title)",
"",
"---Press space to continue---",
};
int i;
clear();
for (i = 0; i < sizeof(txt)/sizeof(*txt); ++i) {
move(i, 0);
printf("%.*s", termwidth()-2, txt[i]);
}
while (getkey() != ' ')
continue;
}
/* Sort the database. */
static int field = -1;
static int entry_cmp(const void *e1, const void *e2) {
const struct lsm_entry *ee1 = e1;
const struct lsm_entry *ee2 = e2;
assert(field >= 0);
return strcmp(ee1->fields[field], ee2->fields[field]);
}
static void sort_database(struct lsm_database *db) {
int f, i;
struct lsm_entry *e;
char dummy[] = "";
f = lsm_field_to_index("Title");
field = lsm_field_to_index(" temp ");
for (i = 0; i < db->nentries; ++i) {
e = &db->entries[i];
if (e->fields[f] == NULL)
e->fields[field] = dummy;
else
e->fields[field] = strtrim(xstrdup(e->fields[f]));
}
qsort(db->entries, db->nentries, sizeof(*db->entries), entry_cmp);
for (i = 0; i < db->nentries; ++i) {
e = &db->entries[i];
if (e->fields[field] != dummy)
free(e->fields[field]);
e->fields[field] = NULL;
}
}
/*
* Return the next duplicate, starting with entry 'i'. Return the index
* of the entry that was found, or 'i' if not found.
*/
static same_title(struct lsm_entry *e1, struct lsm_entry *e2) {
static int title = -1;
char s1[10240], s2[sizeof(s1)];
if (title == -1)
title = lsm_field_to_index("Title");
if (e1->fields[title] == e2->fields[title])
return 1;
if (e1->fields[title] == NULL || e2->fields[title] == NULL)
return 0;
strmaxcpy(s1, e1->fields[title], sizeof(s1));
strmaxcpy(s2, e2->fields[title], sizeof(s2));
strtrim(s1);
strtrim(s2);
return strcasecmp(s1, s2) == 0;
}
static int next_duplicate(struct lsm_database *db, int i) {
struct lsm_entry *e1, *e2;
int start, title;
if (i+1 >= db->nentries)
return 0;
title = lsm_field_to_index("Title");
start = i;
e1 = &db->entries[i];
for (; i+1 < db->nentries; ++i) {
e2 = &db->entries[i+1];
if (same_title(e1, e2))
return i+1;
e1 = e2;
}
return start;
}
/*
* Starting with entry `i', search forward (dir=1) or backward (dir=-1)
* for the next entry whose any field contains the substring "pat". Return
* index of entry found, or the starting index if not found. The `i' entry
* is not included in the search.
*/
static int search(struct lsm_database *db, int i, int dir, const char *pat) {
int j, k;
struct lsm_entry *e;
for (j = i+dir; j >= 0 && j < db->nentries; j += dir) {
e = &db->entries[j];
for (k = 0; k < MAX_FIELDS; ++k)
if (e->fields[k] != NULL)
if (strstr(e->fields[k], pat) != NULL)
return j;
}
return i;
}
/*
* Draw an empty box with the upper left corner at (row, col) and being
* h lines high and w chars wide (including the border).
*/
static void empty_box(int row, int col, int w, int h) {
int i;
move(row, col);
printf("+");
for (i = 0; i < w-2; ++i)
printf("-");
printf("+");
for (i = 1; i < h-1; ++i) {
move(row+i, col);
printf("|%*s|", w-2, "");
}
move(row+h-1, col);
printf("+");
for (i = 0; i < w-2; ++i)
printf("-");
printf("+");
}
/*
* Display a message to the user, and wait for a keypress.
*/
static void notify(const char *msg) {
int h, w, borderx, bordery;
const char anykey[] = "Press any key to continue";
w = strlen(msg);
if (w < sizeof(anykey)-2)
w = sizeof(anykey)-2;
w += 4;
h = 5;
bordery = (termheight()-h)/2;
borderx = (termwidth()-w)/2;
empty_box(bordery, borderx, w+2, h+2);
move(bordery+2, borderx+3);
printf("%s", msg);
move(bordery+4, borderx+3);
printf(anykey);
(void) getkey();
}
/*
* Ask a yes/no question of the user. Return 0 for no, 1 for yes.
*/
static int yesno(const char *msg) {
int key, h, w, borderx, bordery;
const char keys[] = "Y=Ret=Yes N=No";
w = strlen(msg);
if (w < sizeof(keys)-2)
w = sizeof(keys)-2;
w += 4;
h = 5;
bordery = (termheight()-h)/2;
borderx = (termwidth()-w)/2;
empty_box(bordery, borderx, w+2, h+2);
move(bordery+2, borderx+3);
printf("%s", msg);
move(bordery+4, borderx+3);
printf(keys);
do {
key = getkey();
} while (key != 'y' && key != 'Y' && key != 'n' && key != 'N' &&
key != '\r' && key != '\n');
return key != 'n' && key != 'N';
}
/*
* Query the user. "ques" is the question; "ans" will get the answer
* (or at most "n" chars of it, including terminating zero). Return
* 0 for default answer, 1 for new answer, -1 for error (probably aborted
* response by user). Let user edit previous answer.
*/
static int query_user(const char *ques, char *ans, size_t n) {
int h, i, w, first, key, len, start, borderx, bordery;
w = termwidth()-6;
h = 7;
bordery = (termheight()-h)/2;
borderx = (termwidth()-w)/2;
empty_box(bordery, borderx, w+2, h+2);
move(bordery+2, borderx+3);
printf("%s", ques);
move(bordery+5, borderx+3);
for (i = 0; i < w-4; ++i)
printf("-");
move(bordery+7, borderx+3);
printf("Ret=Accept Esc=Cancel");
start = 0;
i = len = strlen(ans);
first = 1;
for (;;) {
if (i < start)
start = i;
else if (i-start >= w-4)
start = i-(w-4)+1;
move(bordery+4, borderx+3);
if (first)
standout();
printf("%-*.*s", w-4, w-4, ans+start);
standend();
move(bordery+4, borderx+3+i-start);
key = getkey();
switch (key) {
case '\n':
case '\r':
return len > 0;
case '\033': /* esc */
return -1;
case '\002': /* ctrl-b */
if (i > 0)
--i;
break;
case '\006': /* ctrl-f */
if (i < len)
++i;
break;
case '\001': /* ctrl-a */
i = 0;
break;
case '\005': /* ctrl-e */
i = len;
break;
case '\b': /* ctrl-h */
case '\177': /* DEL */
if (first) {
i = len = 0;
ans[0] = '\0';
} else if (i > 0) {
--i;
strdel(ans+i, 1);
}
break;
case '\004': /* ctrl-d */
if (first) {
i = len = 0;
ans[0] = '\0';
} else if (i < len)
strdel(ans+i, 1);
break;
default:
if (first) {
i = len = 0;
ans[0] = '\0';
}
if (i < n-1 && isprint(key)) {
strcins(ans+i, key);
++len;
++i;
}
break;
}
first = 0;
}
}
/*
* Signal and exit handlers
*/
static jmp_buf exitjmp;
static sig_atomic_t stilljump = 0;
static void terminate(int dummy) {
stilljump = 0;
longjmp(exitjmp, 1);
}
static void do_exit(void) {
if (stilljump)
longjmp(exitjmp, 1);
}
/*
* The database and the current location in it
*/
static struct lsm_database db;
static int top;
static int current;
static int modified = 0;
static int more;
/*
* Display hlep line at the top and status line at the bottom.
*/
static void help_and_status(void) {
char buf[1024];
int w;
w = termwidth() - 2;
move(0, 0);
clrtoeol();
standout();
printf("%-*s", w, " N=Next P=Prev Q=Quit /=Search forward ?=Backward");
standend();
sprintf(buf, " Entry %lu/%lu (%s)", (unsigned long) current+1,
(unsigned long) db.nentries, more ? "more" : "bottom");
if (modified)
strcat(buf, " ** MODIFIED **");
move(termheight()-1, 0);
clrtoeol();
standout();
printf("%-*s", w, buf);
standend();
}
/*
* Display a list of strings on the screen. Let user scroll through it.
* Return the key the user pressed that wasn't a scrolling key. *cur
* is the location of the cursor (cur == NULL means no cursor), *top means
* the topmost visible string (top == NULL means start at top); both are
* updated when the user scrolls.
*/
static int display_strings(char **list, int n, int *cur, int *top) {
int i, w, col, dirty, dummy_top, key, max_visible, top_row;
if (cur == NULL)
col = 0;
else
col = 4;
if (top == NULL) {
dummy_top = 0;
cur = top = &dummy_top;
}
w = termwidth()-2 - col;
top_row = 2;
max_visible = termheight()-4;
dirty = 1;
for (;;) {
if (*cur < *top) {
*top = *cur;
dirty = 1;
} else if (*cur >= max_visible && *top < *cur-max_visible+1) {
*top = *cur - max_visible + 1;
dirty = 1;
}
if (dirty) {
for (i = 0; i < max_visible; ++i) {
move(top_row + i, 0);
clrtoeol();
move(top_row + i, col);
if (*top + i < n)
printf("%-*s", w, list[*top + i]);
}
dirty = 0;
}
if (col > 0) {
move(top_row + *cur - *top, 0);
printf("-->");
}
help_and_status();
key = getkey();
if (col > 0) {
move(top_row + *cur - *top, 0);
printf(" ");
}
switch (key) {
case KEY_UP:
case 'p':
case 'P':
if (col == 0)
return KEY_UP;
if (*cur > 0)
--(*cur);
break;
case KEY_DOWN:
case 'n':
case 'N':
if (col == 0)
return KEY_DOWN;
if (*cur+1 < n)
++(*cur);
break;
case KEY_NEXT:
case ' ':
if (*top+1 < n) {
*top += max_visible-2;
if (*top >= n)
*top = n-1;
*cur = *top;
dirty = 1;
}
break;
case KEY_PREV:
case '-':
if (*top > 0) {
*top -= max_visible-2;
if (*top < 0)
*top = 0;
*cur = *top;
dirty = 1;
}
break;
default:
return key;
}
}
}
/*
* Build list of strings from the titles; one per string. Return a
* pointer to a static list that will overwritten or modified or
* reallocated or otherwise mucked with by the next call to this function.
* Return NULL if something failed.
*/
static char **build_title_list(struct lsm_database *db, int *count) {
char *list[10240];
int i, title;
title = lsm_field_to_index("Title");
for (i = 0; i < db->nentries; ++i) {
char *p = db->entries[i].fields[title];
list[i] = strdup(p == NULL ? "" : p);
if (list[i] == NULL) {
while (--i >= 0)
free(list[i]);
return NULL;
}
strtrim(list[i]);
}
*count = db->nentries;
return list;
}
/*
* Build a list of strings from one LSM entry. Return a
* pointer to a static list that will overwritten or modified or
* reallocated or otherwise mucked with by the next call to this function.
* Return NULL if something failed.
*/
static char **build_entry_strings(struct lsm_entry *e, int *count) {
static char *list[10240];
static int n;
char *p, *q, *s, buf[10240];
int i, w, len, startcol;
while (--n >= 0)
free(list[n]);
n = 0;
startcol = 16;
w = termwidth() - 2;
for (i = 0; i < MAX_FIELDS; ++i) {
if (e->fields[i] == NULL)
continue;
sprintf(buf, "%s:", lsm_index_to_field(i));
s = e->fields[i];
do {
for (len = strlen(buf); len < startcol; ++len)
buf[len] = ' ';
buf[len] = '\0';
while (*s != '\n' && isspace(*s))
++s;
p = s + strcspn(s, "\n");
sprintf(buf+len, "%.*s", (int)(p-s), s);
len = strlen(buf);
p = buf;
while (len > 0) {
if (len > w) {
q = p + w;
while (q > p && !isspace(*q))
--q;
if (q == p)
q = p + w;
} else
q = p + len;
list[n] = strndup(p, q-p);
if (list[n] == NULL) {
while (--n >= 0) free(list[n]);
n = 0;
return NULL;
}
++n;
len -= q-p;
p = q;
}
buf[0] = '\0';
s = (*p == '\n') ? p+1 : p;
} while (*s != '\0');
}
*count = n;
return list;
}
int main(int argc, char **argv) {
int i, n, build_titles, ntitles, done, key, pg, list_mode;
char pat[10240];
char fname[10240];
char dbname[10240];
FILE *f;
char **list, **titles;
__set_liberror(__exit_on_error | __complain_on_error);
set_progname(argv[0], "lsmtool");
if (argc != 2)
errormsg(1, 0, "usage: %s lsmdatabase\n", get_progname());
if (strlen(argv[1]) >= sizeof(dbname))
errormsg(1, 0, "filename too long, max = %lu",
(unsigned long) sizeof(dbname));
strcpy(dbname, argv[1]);
f = fopen(dbname, "r");
if (f != NULL) {
if (lsm_read_database(f, &db) == -1)
errormsg(1, 0, "error in database `%s'", dbname);
xfclose(f);
}
modified = 0;
terminit();
if (setjmp(exitjmp) != 0) {
clear();
termend();
return EXIT_FAILURE;
}
signal(SIGINT, terminate);
signal(SIGTSTP, SIG_IGN);
stilljump = 1;
atexit(do_exit);
top = 0;
current = 0;
done = 0;
pg = 1;
list_mode = 1;
build_titles = 1;
clear();
while (!done) {
help_and_status();
if (list_mode) {
if (build_titles) {
titles = build_title_list(&db, &ntitles);
if (titles == NULL) {
clear();
termend();
errormsg(1, -1, "failed building "
"titles, out of memory?");
exit(EXIT_FAILURE);
}
build_titles = 0;
}
key = display_strings(titles, ntitles, ¤t, &top);
} else {
list = build_entry_strings(&db.entries[current], &n);
if (list == NULL) {
clear();
termend();
errormsg(1, 0,
"failed building list, out of memory?");
exit(EXIT_FAILURE);
}
key = display_strings(list, n, NULL, NULL);
}
switch (key) {
case '\f':
clear();
break;
case '\r': case '\n':
case 'i': case 'I':
case '+':
list_mode = !list_mode;
break;
case 'h': case 'H':
help();
clear();
break;
case 'q': case 'Q':
done = !modified ||
yesno("Database has been modified, quit anyway?");
break;
case KEY_DOWN:
case 'n': case 'N':
if (current+1 < db.nentries)
++current;
break;
case KEY_UP:
case 'p': case 'P':
if (current > 0)
--current;
break;
case 'g':
current = 0;
pg = 1;
break;
case 'G':
if (db.nentries == 0)
current = 0;
else
current = db.nentries - 1;
pg = 1;
break;
case '\\':
case '?':
if (query_user("Search backward for what?", pat, sizeof(pat)) == -1)
break;
i = search(&db, current, -1, pat);
if (i == current)
notify("Not found");
else
current = i;
break;
case '/':
if (query_user("Search forward for what?", pat, sizeof(pat)) == -1)
break;
i = search(&db, current, 1, pat);
if (i == current)
notify("Not found");
else
current = i;
break;
case 'w':
case 'W':
if (current == db.nentries) {
notify("No current entry");
break;
}
if (query_user("Append entry to which file?", fname, sizeof(fname)) == -1)
break;
if ((f = fopen(fname, "a")) == NULL) {
notify("Couldn't open the file");
break;
}
if (lsm_write_one_entry(f, &db.entries[current]) == -1)
notify("The write failed");
(void) fclose(f);
break;
case 's':
case 'S':
if (query_user("Save to which file?", dbname, sizeof(dbname)) == -1)
break;
if ((f = fopen(dbname, "w")) == NULL) {
notify("Couldn't open the file");
break;
}
if (lsm_write_database(f, &db) == -1)
notify("The save failed");
else
modified = 0;
(void) fclose(f);
break;
case 'd':
case 'D':
if (current < db.nentries) {
memdel(&db.entries[current],
(db.nentries-current)*sizeof(*db.entries),
sizeof(*db.entries));
--db.nentries;
if (current > 0 && current == db.nentries)
--current;
build_titles = 1;
}
modified = 1;
break;
case '$':
sort_database(&db);
build_titles = 1;
modified = 1;
break;
case 'u':
case 'U':
current = next_duplicate(&db, current);
break;
}
}
stilljump = 0;
clear();
termend();
return 0;
}